<?php

/* ========================================================================= */
/* This file is part of the HypnoPHP Framework Library                       */
/* Copyright (c) 2009-2010, Radu Buzea                                       */
/* ========================================================================= */

/**
* @author Radu Buzea
* @copyright Copyright (c) 2009-2010, Radu Buzea (www.hypnosis.ro)
* @license GNU Lesser General Public License (www.gnu.org/copyleft/lesser.html)
* @package HypnoPHP
* @category Core
* @version 1.0.3
*/

// ============================================================================
// Abstract class: HBaseObject
// ============================================================================

abstract class HBaseObject
{

	static $__instances = 1;
	protected $__instance_id = 1;
	protected $__properties = null;
	protected $__readonly_properties = null;
	protected $__errors = null;
	protected $__trigger_errors = false;

	# ###########################################################################

	/**
	* Class construct
	*
	* @return HBaseObject
	*/
	public function __construct()
	{
		if (method_exists($this, '__initialize')) {
			if (func_num_args() > 0) {
				$this->__initialize(func_get_args());
			} else {
				$this->__initialize();
			}
		}
		$this->__trigger_errors = (defined('HGLOBAL_TRIGGER_ERRORS') && HGLOBAL_TRIGGER_ERRORS);
	}

	# ###########################################################################

	/**
	* Class destructor
	*
	* @return void
	*/
	public function __destruct()
	{
		unset($this);
	}

	# ###########################################################################

	/**
	* Add an error
	*
	* @param string $error_text The error message
	* @param integer $error_type The error code
	* @return void
	*/
	protected function addError($error_text = '', $error_type = 0)
	{
		$class_name = get_class($this);
		$error_text = trim($error_text);
		if ($error_type == 0 || $error_text == '') { return false; }
		$log_time = date('Y-m-d H:i:s');
		$log_type = 'UNKNOWN';
		switch ($error_type) {
			case 1:     $log_type = 'E_ERROR'; break;
			case 2:     $log_type = 'E_WARNING'; break;
			case 4:     $log_type = 'E_PARSE'; break;
			case 8:     $log_type = 'E_NOTICE'; break;
			case 16:    $log_type = 'E_CORE_ERROR'; break;
			case 32:    $log_type = 'E_CORE_WARNING'; break;
			case 64:    $log_type = 'E_COMPILE_ERROR'; break;
			case 128:   $log_type = 'E_COMPILE_WARNING'; break;
			case 256:   $log_type = 'E_USER_ERROR'; break;
			case 512:   $log_type = 'E_USER_WARNING'; break;
			case 1024:  $log_type = 'E_USER_NOTICE'; break;
			case 2048:  $log_type = 'E_STRICT'; break;
			case 4096:  $log_type = 'E_RECOVERABLE_ERROR'; break;
			case 8192:  $log_type = 'E_DEPRECATED'; break;
			case 16384: $log_type = 'E_USER_DEPRECATED'; break;
			case 30719: $log_type = 'E_ALL'; break;
		}
		$log_line = sprintf("%s\t%s\t:\t%s", $log_time, $log_type, $error_text);

		if (is_null($this->__errors) || !is_array($this->__errors)) {
			$this->__errors = array();
		}
		if (defined('HGLOBAL_LOG_ERRORS') && HGLOBAL_LOG_ERRORS) {
			global $_global_errors_log;
			if (!isset($_global_errors_log[$class_name]) || !is_array($_global_errors_log[$class_name])) {
				$_global_errors_log[$class_name] = array();
			}
			array_push($_global_errors_log[$class_name], $log_line);
		} else {
			if (is_null($this->__errors) || !is_array($this->__errors)) {
				$this->__errors = array();
			}
			array_push($this->__errors, $log_line);
		}
		$debug_link = false;
		if ($this->__trigger_errors) {
			trigger_error($error_text, $error_type);
		}
	}

	# ###########################################################################

	/**
	* Get a list of errors (if any)
	*
	* @return array
	*/
	public function getErrors()
	{
		if (defined('HGLOBAL_LOG_ERRORS') && HGLOBAL_LOG_ERRORS) {
			global $_global_errors_log;
			$class_name = get_class($this);
			return $_global_errors_log[$class_name];
		} else if (!is_null($this->__errors) && is_array($this->__errors)) {
			return $this->__errors;
		} else {
			return array();
		}
	}

	# ###########################################################################

	/**
	* Count errors
	*
	* @return integer
	*/
	public function errorCount()
	{
		return count($this->getErrors());
	}

	# ###########################################################################

	/**
	* Get an error record from the object errors list
	*
	* @param integer $error_index (-1 returns the last registered error)
	* @return array The error record
	*/
	public function getError($error_index = -1)
	{
		if ($err_index < 0) {
			$err_index = $this->errorCount() - 1;
			if ($err_index < 0) { return null; }
		}
		$errors = $this->getErrors();
		if ($err_index > count($errors) - 1) { return null; }
		return $errors[$err_index];
	}

	# ###########################################################################

	/**
	* Get the last registered error
	*
	* @return array The error record
	*/
	public function getLastError()
	{
		return $this->getError(-1, $filter_err_type);
	}

	# ###########################################################################

	/**
	* Set a member name that must be treat as read-only member
	*
	* @param string $param The member name
	* @return void
	*/
	protected function setReadonlyProperty($param = '')
	{
		if (is_null($this->__readonly_properties) || !is_array($this->__readonly_properties)) {
			$this->__readonly_properties = array();
		}
		if ($this->__checkVarName($param, true) && !in_array($param, $this->__readonly_properties)) {
			array_push($this->__readonly_properties, $param);
		}
	}

	# ###########################################################################

	/**
	* Set one or more member names that must be treat as read-only members
	*
	* @return void
	*/
	protected function setReadonlyProperties()
	{
		if (is_null($this->__readonly_properties) || !is_array($this->__readonly_properties)) {
			$this->__readonly_properties = array();
		}
		if (func_num_args() == 0) { return; }
		$args = func_get_args();
		foreach ($args as $arg_params) {
			if (is_string($arg_params)) {
				$arg_params = preg_split("/[\s,\.\;]+/", $arg_params);
			}
			if (is_array($arg_params) && count($arg_params)) {
				foreach ($arg_params as $param) {
					if (is_string($param)) {
						$param = strtolower($param);
						if ($this->__checkVarName($param, true) &&
							!in_array($param, $this->__readonly_properties)) {
							array_push($this->__readonly_properties, $param);
						}
					}
				}
			}
		}
	}

	# ###########################################################################

	/**
	* Get the list of object properties (virtual members)
	*
	* @return array
	*/
	protected function getProperties()
	{
		if (is_null($this->__properties) || !is_array($this->__properties)) {
			$this->__properties = array();
		}
		return $this->__properties;
	}

	# ###########################################################################

	/**
	* Get the list of object read-only properties (virtual read-only members)
	*
	* @return array
	*/
	protected function getReadonlyProperties()
	{
		if (is_null($this->__readonly_properties) || !is_array($this->__readonly_properties)) {
			$this->__readonly_properties = array();
		}
		return $this->__readonly_properties;
	}

	# ###########################################################################

	/**
	* Creating a copy of an object with replicated properties
	*
	*	@return object (HBaseObject)
	*/
	public function __clone()
	{
		$this->__instance_id = ++self::$__instances;
		return $this;
	}

	# ###########################################################################

	/**
	* Class to string conversion
	*
	* @return string
	*/
	public function __toString()
	{
		return get_class($this);
	}

	# ###########################################################################

	/**
	* Invoking inaccessible methods in the object context
	*
	* @param string $func_name The method name
	* @param mixed $func_args An enumerated array containing the parameters passed to method
	*/
	public function __call($func_name = '', $func_args = array())
	{
		$class_name = get_class($this);
		if (!in_array($func_name, get_class_methods($class_name))) {
			$this->addError("The <i>{$class_name}->{$func_name}()</i> method not exists",
				E_USER_ERROR);
		}
	}

	# ###########################################################################

	/**
	* Global members overloading - Getter method
	*
	* @param string $param The object property name
	*/
	public function __get($param = '')
	{
		$param = strtolower(trim($param));
		if ($param == '') { return null; }
		$get_method_name = '__get_' . $param;
		if (method_exists($this, $get_method_name)) {
			return $this->$get_method_name();
		}
		if (is_null($this->__properties) || !is_array($this->__properties)) {
			$this->__properties = array();
		}
		if ($param == '__errors') {
			if (!isset($this->__properties['__errors']) || !is_array($this->__properties['__errors'])) {
				$this->__properties['__errors'] = array();
			}
		}
		if (substr($param, 0, 2) == '__' && (isset($this->$param) || isset($this->__properties[$param]))) {
			$class_name = get_class($this);
			$this->addError("The <i>{$class_name}->{$param}</i> property is protected", E_USER_NOTICE);
		}
		if (!array_key_exists($param, $this->__properties)) {
			$class_name = get_class($this);
			$backtrace = debug_backtrace();
			$dfn = $backtrace[0]['file'];
			$dln = $backtrace[0]['line'];
			$this->addError("Undefined property <i>{$class_name}::{$param}</i> in <b>{$dfn}</b> on line <b>{$dln}</b>", E_USER_NOTICE);
			return null;
		}
		return $this->__properties[$param];
	}

	# ###########################################################################

	/**
	* Global members overloading - Setter method
	*
	* @param string $param The object member name
	* @param mixed $value The object member value
	*/
	public function __set($param = '', $value = null)
	{
		$param = trim($param);
		if ($param == '') { return; }
		$set_method_name = '__set_' . $param;
		if (method_exists($this, $set_method_name)) {
			return $this->$set_method_name($value);
		}
		if (in_array($param, array('__trigger_errors', '__errors', '__properties', '__readonly_properties'))) {
			trigger_error("The <i>{$param}</i> is a HBaseObject protected property", E_USER_WARNING);
			return null;
		}
		if (method_exists($this, '__get_' . $param)) {
			$class_name = get_class($this);
			$this->addError("The <i>{$class_name}::{$param}</i> property is read-only", E_USER_NOTICE);
			return;
		}
		if (is_null($this->__properties) || !is_array($this->__properties)) {
			$this->__properties = array();
		}
		$param = strtolower($param);
		if (substr($param, 0, 2) == '__' && (isset($this->$param) || isset($this->__properties[$param]))) {
			$class_name = get_class($this);
			$this->addError("The <i>{$class_name}::{$param}</i> property is protected", E_USER_NOTICE);
		}
		if (is_array($this->__readonly_properties) && in_array($param, $this->__readonly_properties)) {
			$class_name = get_class($this);
			$this->addError("The <i>{$class_name}::{$param}</i> property is read-only",
				E_USER_NOTICE);
			return;
		}
		$this->__properties[$param] = $value;
	}

	# ###########################################################################

	/**
	* Check if a member exists
	*
	* @param string $param The object member name
	* @return bool
	*/
	public function __isset($param = '')
	{
		$param = strtolower($param);
		return (is_array($this->__properties) && isset($this->__properties[$param]));
	}

	# ###########################################################################

	/**
	* Unset a member
	*
	* @param string $param The object member name
	* @return void
	*/
	public function __unset($param = '')
	{
		$param = strtolower($param);
		if (is_array($this->__readonly_properties) && in_array($param, $this->__readonly_properties)) {
			$this->addError("Cannot unset the read-only property <i>{$class_name}.{$param}</i>", E_USER_NOTICE);
		}
		if (isset($this->__properties[$param])) {
			unset($this->__properties[$param]);
		}
	}

	# ###########################################################################

	/**
	* Check a member name (variable and/or property)
	*
	* @param string $name The member name
	* @param bool $warning_call (optional) Call a warning if the name is invalid
	*/
	protected function __checkVarName($name = '', $warning_call = false)
	{
		if (!is_string($name) || !preg_match("/^[a-zA-Z_][a-zA-Z0-9_]*$/", $name)) {
			if ($warning_call) {
				$this->addError("Invalid variable name '<em>$name</em>'", E_USER_WARNING);
			}
			return false;
		}
		return true;
	}

	# ###########################################################################

	/**
	* Sigleton implementation
	*
	* @param string $class_name
	* @return object (HBaseObject)
	*/
	public static function &singleton($class_name = '')
	{
		static $instances = array();
		$class_name = trim($class_name);
		if ($class_name == '') {
			trigger_error("Empty class name (warning from <i>" . __CLASS__ . "::get_instance</i>)",
				E_USER_WARNING);
			return null;
		}
		if (!class_exists($class_name, false)) {
			trigger_error("Class <i>{$class_name}</i> not exists (error from <i>" . __CLASS__ .
				"::get_instance</i>)", E_USER_ERROR);
			return null;
		}
		if (!array_key_exists($class_name, $instances)) {
			$instances[$class_name] = new $class_name;
		}
		$singleton =& $instances[$class_name];
		return $singleton;
	}

	# ###########################################################################

}
